package com.vois.jack.btmgr.util;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import com.vois.jack.btmgr.common.State;
import com.vois.jack.btmgr.common.StateMachine;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;

public class ActionProcessUtil extends StateMachine {
    private Logger logger = Logger.getLogger(ActionProcessUtil.class);
    InitState initState;
    ExecutingActionState executingActionState;
    WaitingResultState waitingResultState;
    Queue<ActionRequest> actionRequestQueue;
    ActionRequest curActionRequest;

    public interface ActionCallback {
        boolean onProcess(Object context, Bundle parameters);
        void onResult(Object context, Bundle data);
        void onError(Object context, int errorCode);
        void onTimeout(Object context);
    }

    public class ActionRequest {
        ActionMessage message;
        Object context;
        Bundle parameters;
        int timeout;
        boolean working;
        ActionCallback callback;

        public ActionMessage getMessage() {
            return message;
        }

        public void setMessage(ActionMessage message) {
            this.message = message;
        }

        public Bundle getParameters() {
            return parameters;
        }

        public void setParameters(Bundle parameters) {
            this.parameters = parameters;
        }

        public int getTimeout() {
            return timeout;
        }

        public void setTimeout(int timeout) {
            this.timeout = timeout;
        }

        public ActionCallback getCallback() {
            return callback;
        }

        public void setCallback(ActionCallback callback) {
            this.callback = callback;
        }

        public Object getContext() {
            return context;
        }

        public void setContext(Object context) {
            this.context = context;
        }
    }

    public enum ActionMessage {
        ACT_ADD_ACTION_MSG,
        ACT_EXE_ACTION_MSG,
        ACT_EXE_ACTION_TIMEOUT_MSG,
        ACT_EXE_ACTION_RESULT_MSG,
        ACT_EXE_ACTION_RESULT_ERROR,
        ACT_STOP
    }

    class InitState extends State {
        @Override
        public void enter() {
            logger.d( "enter: InitState");
            deferMessage(obtainMessage(ActionMessage.ACT_EXE_ACTION_MSG.ordinal()));
            transitionTo(executingActionState);
        }

        @Override
        public void exit() {
            logger.d( "exit: InitState");
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            ActionMessage message = ActionMessage.values()[msg.what];
            logger.d( "processMessage: InitState:" + message);

            switch (message) {
                case ACT_STOP: {
                    quitNow();
                    ret = true;
                }
                break;
            }

            return ret;
        }
    }

    class ExecutingActionState extends State {
        @Override
        public void enter() {
            logger.d( "enter: ExecutingActionState");
            actionRequestQueue = new LinkedList<>();
        }

        @Override
        public void exit() {
            logger.d( "exit: ExecutingActionState");
            actionRequestQueue.clear();
            actionRequestQueue = null;
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            ActionMessage message = ActionMessage.values()[msg.what];
            logger.d( "processMessage: ExecutingActionState:" + message);
            
            switch (message) {
                case ACT_ADD_ACTION_MSG: {
                    ActionRequest actionRequest = (ActionRequest) msg.obj;
                    actionRequestQueue.offer(actionRequest);

                    if (curActionRequest == null) {
                        sendMessage(ActionMessage.ACT_EXE_ACTION_MSG.ordinal());
                    }

                    ret = true;
                }
                break;

                case ACT_EXE_ACTION_MSG: {
                    if (!actionRequestQueue.isEmpty()) {
                        curActionRequest = actionRequestQueue.poll();

                        if (curActionRequest != null) {
                            if (!curActionRequest.callback.onProcess(curActionRequest.getContext(), curActionRequest.getParameters())) {
                                curActionRequest.callback.onError(curActionRequest.getContext(), -1);
                                curActionRequest = null;
                                sendMessage(ActionMessage.ACT_EXE_ACTION_MSG.ordinal());
                            } else {
                                transitionTo(waitingResultState);
                            }
                        }
                    }

                    ret = true;
                }
                break;
            }

            return ret;
        }
    }

    class WaitingResultState extends State {
        private Timer actionExecuteTimer;
        @Override
        public void enter() {
            logger.d( "enter: WaitingResultState");
            actionExecuteTimer = new Timer();
            actionExecuteTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    sendMessage(ActionMessage.ACT_EXE_ACTION_TIMEOUT_MSG.ordinal());
                }
            }, curActionRequest.timeout);
        }

        @Override
        public void exit() {
            logger.d( "exit: WaitingResultState");
            if (actionExecuteTimer != null) {
                actionExecuteTimer.cancel();
                actionExecuteTimer = null;
            }
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            ActionMessage message = ActionMessage.values()[msg.what];
            logger.d( "processMessage: WaitingResultState:" + message);

            switch (message) {
                case ACT_EXE_ACTION_RESULT_MSG: {
                    Object context = msg.obj;
                    Bundle result = msg.getData();

                    if (actionExecuteTimer != null) {
                        actionExecuteTimer.cancel();
                        actionExecuteTimer = null;
                    }

                    if (curActionRequest != null && curActionRequest.callback != null) {
                        curActionRequest.callback.onResult(context, result);
                    }

                    curActionRequest = null;
                    deferMessage(obtainMessage(ActionMessage.ACT_EXE_ACTION_MSG.ordinal()));
                    transitionTo(executingActionState);

                    ret = true;
                }
                break;

                case ACT_EXE_ACTION_RESULT_ERROR: {
                    Object context = msg.obj;
                    int errorCode = msg.arg1;

                    if (actionExecuteTimer != null) {
                        actionExecuteTimer.cancel();
                        actionExecuteTimer = null;
                    }

                    if (curActionRequest != null && curActionRequest.callback != null) {
                        curActionRequest.callback.onError(context, errorCode);
                    }

                    curActionRequest = null;
                    deferMessage(obtainMessage(ActionMessage.ACT_EXE_ACTION_MSG.ordinal()));
                    transitionTo(executingActionState);

                    ret = true;
                }
                break;

                case ACT_EXE_ACTION_TIMEOUT_MSG: {
                    if (actionExecuteTimer != null) {
                        actionExecuteTimer.cancel();
                        actionExecuteTimer = null;
                    }

                    if (curActionRequest != null && curActionRequest.callback != null) {
                        curActionRequest.callback.onTimeout(msg.obj);
                    }

                    curActionRequest = null;

                    deferMessage(obtainMessage(ActionMessage.ACT_EXE_ACTION_MSG.ordinal()));
                    transitionTo(executingActionState);

                    ret = true;
                }
                break;
            }

            return ret;
        }
    }

    private void init() {
        initState = new InitState();
        executingActionState = new ExecutingActionState();
        waitingResultState = new WaitingResultState();

        addState(initState);
        addState(executingActionState, initState);
        addState(waitingResultState, initState);

        setInitialState(initState);
    }

    public ActionProcessUtil(String name, Handler handler) {
        super(name, handler);
        init();
    }

    public ActionProcessUtil(String name) {
        super(name);
        init();
    }

    public void addActionRequest(Object context, Bundle parameters, int actionTimeout, ActionCallback callback) {
        logger.d( "addActionRequest");
        ActionRequest actionRequest = new ActionRequest();
        actionRequest.setContext(context);
        actionRequest.setParameters(parameters);
        actionRequest.setCallback(callback);
        actionRequest.setTimeout(actionTimeout);

        sendMessage(ActionMessage.ACT_ADD_ACTION_MSG.ordinal(), actionRequest);
    }

    public void reportResult(Object context, Bundle result) {
        Message message = obtainMessage(ActionMessage.ACT_EXE_ACTION_RESULT_MSG.ordinal(), context);
        message.setData(result);
        sendMessage(message);
    }

    public void reportError(Object context, int errorCode) {
        sendMessage(ActionMessage.ACT_EXE_ACTION_RESULT_ERROR.ordinal(), errorCode, 0, context);
    }

    public void startActionExecution() {
        start();
    }

    public void stopActionExecution() {
        sendMessage(ActionMessage.ACT_STOP.ordinal());
    }
}
